﻿using System;
using System.Collections.Generic;
using System.ServiceModel.Syndication;
using HelperTools;
using Interfaces.FilterModels;
using Interfaces.Filters;
using Interfaces.Models;
using System.Linq;

namespace BusinessLogic.Models.Filter
{
    public sealed class SyndicationItemsFilter : NewsFilterBase<SyndicationItem, INewsFilterModel>, INewsFilter<INewsFilterModel>
    {
        public SyndicationItemsFilter(INewsFilterModel filterModel) : base(new LinkedList<Func<SyndicationItem, bool>>(), filterModel)
        {
           InitializeFilterCollection();
        }
        
        public IEnumerable<object> Filtrate(IEnumerable<object> source)
        {
            var typedSource = source as IEnumerable<SyndicationItem>;
            //apply such filter from filter collection for typedSource collection
            var filteredCollection = FiltersCollection.Aggregate(typedSource, (current, filter) => current.Where(filter).Select(x => x));
            return filteredCollection;
        }

        private void InitializeFilterCollection()
        {
            FiltersCollection.AddLast(GetDateFilter());
            FiltersCollection.AddLast(GetBaseUriFilter());
            FiltersCollection.AddLast(GetAuthorsFilter());
            FiltersCollection.AddLast(GetContributorsFilter());
            FiltersCollection.AddLast(GetTitleFilter());
            FiltersCollection.AddLast(GetSummaryFilter());
            FiltersCollection.AddLast(GetCategoriesFilter());
        }

        private Func<SyndicationItem, bool> GetDateFilter()
        {
            var highPublishDate = FilterModel.HighPublishDate ?? DateTime.MaxValue;
            var lowPublishDate = FilterModel.LowPublishDate ?? DateTime.MinValue;
            return item => item.PublishDate.DateTime <= highPublishDate && item.PublishDate.DateTime >= lowPublishDate;
        }

        private Func<SyndicationItem, bool> GetTitleFilter()
        {
            return item => GetStringPrefixFilterExpression(item.Title.With(x=>x.Text), FilterModel.Title);
        }

        private Func<SyndicationItem, bool> GetBaseUriFilter()
        {
            return item => GetStringPrefixFilterExpression(item.BaseUri.With(x=>x.Host),FilterModel.BaseUri);
        }

        private Func<SyndicationItem, bool> GetSummaryFilter()
        {
            return item => GetStringContainsFilterExpression(item.Summary.With(x=>x.Text),FilterModel.Summary);
        }

        private Func<SyndicationItem, bool> GetAuthorsFilter()
        {
            return item => GetIncludedExcludedPersonCollectionFilterExpression(item.Authors,FilterModel.ExcludedAuthors);
        }

        private Func<SyndicationItem, bool> GetContributorsFilter()
        {
            return item => GetIncludedExcludedPersonCollectionFilterExpression(item.Contributors,FilterModel.ExcludedContributors);
        }

        private Func<SyndicationItem, bool> GetCategoriesFilter()
        {
            return
                item =>
                GetIncludedExcludedCategoriesCollectionFilterExpression(item.Categories,FilterModel.ExcludedeCategory);
        }

        /// <summary>
        /// null or empty excludeCollection all item included in list
        /// {Name = null, Email = null, Uri} all item that have author excluded
        /// {Name = null, Email = value, Uri =null} excluded all item that email equals value
        /// {Name = value,Email = value, Uri = value} excluded only item that has equals Email and Uri and Name
        /// </summary>
        /// <param name="sourcePerson">source list for filtering</param>
        /// <param name="excludedPerson">excluded list</param>
        /// <returns></returns>
        private static bool GetIncludedExcludedPersonCollectionFilterExpression(IEnumerable<SyndicationPerson> sourcePerson, IEnumerable<IPerson> excludedPerson)
        {
            bool isExcluded = sourcePerson.Any(auth => excludedPerson.Returns(y => y.Any(pers => pers.Email.Returns(x=>x.Equals(auth.Email),true)
                                                                                  && pers.Name.Returns(x => x.Equals(auth.Name),true)
                                                                                  && pers.Uri.Returns(x => x.Equals(auth.Uri),true)), false));
            return !isExcluded;
        }

        private static bool GetStringPrefixFilterExpression(string propertyValue, string expression)
        {
            return expression == null || propertyValue.Returns(x=>x.StartsWith(expression),false);
        }

        private static bool GetStringContainsFilterExpression(string propertyValue, string expression)
        {
            return expression == null || propertyValue.Returns(x => x.Contains(expression), false);
        }

        /// <summary>
        /// null or empty excludeCollection all item included in list
        /// {Name = null, Lable = null} all item that have category excluded
        /// {Name = null, Lable = value} excluded all item that email equals value(similar with Name = value and Lable=null)
        /// {Name = value, Lable = value} excluded only item that has equals Name and Lable
        /// </summary>
        /// <param name="sourceCategory">source list for filtering</param>
        /// <param name="excludedCategories">excluded list</param>
        /// <returns></returns>
        private static bool GetIncludedExcludedCategoriesCollectionFilterExpression(IEnumerable<SyndicationCategory> sourceCategory, IEnumerable<ICategory> excludedCategories)
        {
            bool isExcluded = sourceCategory.Any(sndCat => excludedCategories.Returns(y => y.Any(cat => cat.Name.Returns(x => x.Equals(sndCat.Name), true)
                                                                                        && cat.Lable.Returns(x => x.Equals(sndCat.Label), true)), false));
            return !isExcluded;
        }
    }
}